# 【一】参数和方法介绍
"""
# Define here the models for your spider middleware
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals

# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter


# 爬虫中间件
class SpiderscrapySpiderMiddleware:
    # 所有方法不是必须定义 你只需定义你自己想要的方法即可

    @classmethod
    def from_crawler(cls, crawler):
        # 该方法用于创建爬虫实例对象
        s = cls()
        # 向 爬虫对象的 信号连接方法中发送了一个信号 爬虫启动开始的信号
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        # 每一个爬虫的响应会经过当前中间还能

        # response 响应对象
        # spider 爬虫对象

        return None

    def process_spider_output(self, response, result, spider):
        # 在处理完爬虫的响应之后被会被调用

        # response 响应对象
        # result 处理结果
        # spider 爬虫对象

        # 必须是一个 item 对象或者是 request 对象(scrapy 的 request 对象)
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        # 当爬虫中遇到异常会被当前捕获

        # response 响应对象
        # exception 错误信息
        # spider 爬虫对象

        # 返回None / item 对象 / request 对象
        pass

    def process_start_requests(self, start_requests, spider):
        # 在 爬虫启动之后对 初识的URL进行请求处理的方法
        # 对 start_urls = ["https://www.cnblogs.com"] 发起请求

        # 必须返回一个 request 对象不能是 item 对象
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        # 打开爬虫项目的时候会启动

        # 记录了一条启动日志
        spider.logger.info("Spider opened: %s" % spider.name)


# 下载中间件
class SpiderscrapyDownloaderMiddleware:
    # 不需要定义所有的方法 需要哪个写那个

    @classmethod
    def from_crawler(cls, crawler):
        # 创建爬虫项目实例会启动

        s = cls()
        # 向 爬虫对象的 信号连接方法中发送了一个信号 爬虫启动开始的信号
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    # 拦截所有的请求对象
    # 并且对请求对象可以做修改
    def process_request(self, request, spider):
        '''

        :param request: 拦截到的请求对象
        :param spider: 爬虫实例
        :return:
        '''
        # 每一个请求都会经过当前方法

        # 返回值必须是以下之一
        # - return None: 处理本次请求 继续下一次请求
        # - or return a Response object 执行当前的 process_response 方法 将 response 返回给引擎重新调度
        # - or return a Request object # 执行当前的 process_request 方法 将 request 返回给引擎重新调度
        # - or raise IgnoreRequest: 也可以执行当前的 process_exception 方法 抛出异常信息
        return None

    # 拦截所有的响应对象
    def process_response(self, request, response, spider):
        '''

        :param request: 请求对象
        :param response: 响应对象
        :param spider: 爬虫对象
        :return:
        '''
        # 每一次的响应都会经过当前函数

        # 返回值必须是以下之一
        # - return a Response object 把当前的 response 对象返回给引擎重新调度
        # - return a Request object 把当前的 request 对象返回给引擎重新调度
        # - or raise IgnoreRequest 抛出 process_exception 异常信息
        return response

    # 拦截当前项目中发生的错误和异常信息
    def process_exception(self, request, exception, spider):
        '''

        :param request: 请求对象
        :param exception: 异常信息
        :param spider: 爬虫对象
        :return:
        '''
        # 当下载中遇到错误信息的时候会直接经过当前函数

        # 返回值必须是以下之一
        # - return None: 继续处理该异常
        # - return a Response object: 停止 process_exception 操作
        # - return a Request object: 停止 process_exception 操作
        pass

    def spider_opened(self, spider):
        # 记录日志
        spider.logger.info("Spider opened: %s" % spider.name)

"""

# 【二】请求中间件使用
# 【1】向请求头中添加代理
"""
    def process_request(self, request, spider):
        '''

        :param request: 拦截到的请求对象
        :param spider: 爬虫实例
        :return:
        '''
        # 每一个请求都会经过当前方法

        # 返回值必须是以下之一
        # - return None: 处理本次请求 继续下一次请求
        # - or return a Response object 执行当前的 process_response 方法 将 response 返回给引擎重新调度
        # - or return a Request object # 执行当前的 process_request 方法 将 request 返回给引擎重新调度
        # - or raise IgnoreRequest: 也可以执行当前的 process_exception 方法 抛出异常信息
        print(request)
        print(dir(request))
        '''
        &lt;GET https://www.cnblogs.com/huggingface/p/18515419&gt;
        ['attributes', 'body', 'callback', 'cb_kwargs', 'cookies', 'copy', 'dont_filter', 'encoding', 'errback', 'flags', 
        'from_curl', 'headers', 'meta', 'method', 'priority', 'replace', 'to_dict', 'url']
        '''
        # meta 在我们学 Django的时候我们通过 request.META 提取主机地址 ---&gt; 限制同意个IP 1min 只能值允许访问三次

        # 加代理也需要向 meta 属性中添加代理参数
        request.meta["proxy"] = "https://127.0.0.1:9999"
        return None
"""
# 【2】在请求中携带Cookie数据
'''
# 添加Cookie参数
request.cookies = "一串Cookie值"
'''

# 【3】请求头携带
# （1）在你的 settings.py 中有 USER_AGENT 参数
'''
# USER_AGENT 浏览器标识 在这里添加自定义的请求头
# 不加使用默认的请求头 SpiderScrapy (+http://www.yourdomain.com)
USER_AGENT = UserAgent().random
'''

# （2）在每一个请求提交 yield 的时候
"""
yield Request(url=next_url, callback=self.parse)
'''
url: 需要提交请求的目标地址
callback: 回调函数 发起请求后获取到响应对象  对响应对如何处理？
method: str = "GET", 默认的请求方式 GET 
headers: Optional[dict] = 请求头参数
body: Optional[Union[bytes, str]] 请求体数据
cookies: Optional[Union[dict, List[dict]]] 带你的Cookie
meta: Optional[dict] 你可以理解为上下文对象 进入到回调函数后 我想从当前位置向回调函数中传参数 这时候 用 meta 作为中介传递参数
encoding: str = "utf-8" 默认编码格式 
'''
"""

# （3）中间件中携带请求头
'''
# 添加额外的请求头参数
request.headers["User-Agent"] = UserAgent().random
request.headers["Referer"] = "一串 referer 值"
'''
